///////////////////////////////////////////////////////////////////////////////////////////////////
/// \file wavefunction.h
///
/// \brief   header file for the wavefunction class
///
///////////////////////////////////////////////////////////////////////////////////////////////////

#ifndef PCPS_WAVEFUNCTION_HEADER
#define PCPS_WAVEFUNCTION_HEADER

#include<src/pcps.h>
#include<src/input.h>
#include<src/mantissa_exponent.h>
#include<src/wfn_data.h>
#include<src/coeff_obj.h>
#include<src/archive.h>

namespace pcps {

  ///////////////////////////////////////////////////////////////////////////////////////////////////
  /// \brief  A class to represent a wavefunction.
  ///
  /// The wavefunction consists of essentially two parts:  raw data and coefficient functions.
  /// The raw data is stored in WfnData objects accessed by their names in _wi_map and _wf_map,
  /// while the various coefficient functions are stored in CoeffObj objects accessed by their names
  /// in _co_map.
  ///
  /// The overall coefficient of the wavefunction for a given lattice configuration is given by the
  /// coefficient function pointed to by _overall_coeff.
  ///
  ///////////////////////////////////////////////////////////////////////////////////////////////////
  template<class S> class Wavefunction {

    private:

      /// \brief a counter tracking where to put the next fixed integer data in the overall integer data ordering
      int _fixed_int_pos;

      /// \brief a counter tracking where to put the next variable integer data in the overall integer data ordering
      int _var_int_pos;

      /// \brief a counter tracking where to put the next fixed floating point data in the overall integer data ordering
      int _fixed_float_pos;

      /// \brief a counter tracking where to put the next variable floating point data in the overall integer data ordering
      int _var_float_pos;

      /// \brief  the object used to compute the overall wavefunction coefficient
      boost::shared_ptr<pcps::CoeffObj<S> > _overall_coeff;

      /// \brief  A container organizing the coefficient objects by name.
      ///         These are used to build the overall coefficient.
      std::map<std::string, boost::shared_ptr<pcps::CoeffObj<S> > > _co_map;

      /// \brief  a container organizing the integer wavefunction data objects by name
      std::map<std::string, boost::shared_ptr<pcps::WfnData<int> > > _wi_map;

      /// \brief  a container organizing the floating point wavefunction data objects by name
      std::map<std::string, boost::shared_ptr<pcps::WfnData<S> > > _wf_map;

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief Checks that a name is not already used among the wavefunction's constituents.
      ///
      /// \param[in]      name     the name to check
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      void check_new_name(const std::string & name) const {
        if ( _co_map.count(name) == 1 || _wi_map.count(name) == 1 || _wf_map.count(name) == 1 )
          throw pcps::Exception("the wavefunction already contains an object with name \"%s\"") % name;
      }

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief Checks that a name is not already used among the wavefunction's constituents.
      ///
      /// \param[in,out]  arch          an archive from which to read the data object
      /// \param[in,out]  next_fix_pos  the position in which to place the next set of fixed data
      /// \param[in,out]  next_var_pos  the position in which to place the next set of variable data
      /// \param[in,out]  m             the mapping between names and data objects
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      template<class T> void add_wfn_data(pcps::Archive & arch,
                                          int & next_fix_pos,
                                          int & next_var_pos,
                                          std::map<std::string, boost::shared_ptr<pcps::WfnData<T> > > & m) {

        // create the data object
        boost::shared_ptr<pcps::WfnData<T> > d( new pcps::WfnData<T>(arch) );
        
        // make sure the name is not already in use
        this->check_new_name(d->name());

        // set the data position
        if (d->fixed()) {
          d->set_pos(next_fix_pos);
          next_fix_pos -= d->size();
        } else {
          d->set_pos(next_var_pos);
          next_var_pos += d->size();
        }

        // add the data object to the name-data mapping
        assert( m.count(d->name()) == 0 );
        m[d->name()] = d;

      }

    public:

      template<class T> void test_print(pcps::Archive & arch, const std::map<std::string, boost::shared_ptr<T> > & m) {
        typename std::map<std::string, boost::shared_ptr<T> >::const_iterator it;
        for (it = m.begin(); it != m.end(); it++)
          arch << *it->second;
      }

      ~Wavefunction() {
        //if ( typeid(S) == typeid(double) ) {
        //  pcps::TextFileArchive a("temp.txt", "wt");
        //  this->test_print(a, _wi_map);
        //  this->test_print(a, _wf_map);
        //  this->test_print(a, _co_map);
        //  assert( _overall_coeff.get() != 0 );
        //  a << pcps::OverallCoeffCommand::keyword() << std::endl;
        //  a << _overall_coeff->name() << std::endl;
        //  a << std::endl;
        //  //pcps::BinaryFileArchive b("temp.bin", "wt");
        //  //this->test_print(b, _wi_map);
        //  //this->test_print(b, _wf_map);
        //}
      }

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief constructs an empty wavefunction
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      Wavefunction() : _fixed_int_pos(-1), _var_int_pos(0), _fixed_float_pos(-1), _var_float_pos(0) {}

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief Adds an integer data object to the wavefunction.
      ///
      /// \param[in,out]  arch     an archive from which to read the data object
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      void add_wfn_int_data(pcps::Archive & arch) {
        this->add_wfn_data(arch, _fixed_int_pos, _var_int_pos, _wi_map);
      }

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief Adds a floating point data object to the wavefunction.
      ///
      /// \param[in,out]  arch     an archive from which to read the data object
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      void add_wfn_float_data(pcps::Archive & arch) {
        this->add_wfn_data(arch, _fixed_float_pos, _var_float_pos, _wf_map);
      }

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief Adds a coefficient object of type T to the wavefunction.
      ///
      /// \param[in]      userinp  the user's input options
      /// \param[in,out]  arch     an archive from which to read the coefficient object
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      template<class T> void add_coeff_obj(const pcps::Input & userinp, pcps::Archive & arch) {

        // create the coefficient object
        boost::shared_ptr<pcps::CoeffObj<S> > c( new T(userinp, arch, _wi_map, _wf_map, _co_map) );
        
        // make sure the name is not already in use
        this->check_new_name(c->name());

        // add the coefficient object to the name-coefficient mapping
        this->_co_map[c->name()] = c;

      }

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief sets the overall coefficient for the wavefunction
      ///
      /// \param[in]      s        the name of the coefficient object to use
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      void set_overall_coefficient(const std::string & s) {
        if ( _co_map.count(s) == 0 )
          throw pcps::Exception("no coefficient object with the name \"%s\" in Wavefunction::set_overall_coefficient.") % s;
        _overall_coeff = _co_map[s];
      }

      /// \brief  returns a pointer to the overall wavefunction coefficient
      boost::shared_ptr<pcps::CoeffObj<S> > & overall_coefficient() { return _overall_coeff; }

      ///////////////////////////////////////////////////////////////////////////////////////////////////
      /// \brief  returns a reference to the named floating point wavefunction data
      ///
      /// \param[in]      name     the name of the data we want
      ///
      ///////////////////////////////////////////////////////////////////////////////////////////////////
      pcps::WfnData<S> & get_fdata(const std::string & name) {
        if ( _wf_map.count(name) == 0 )
          throw pcps::Exception("no floating point data object with the name \"%s\" in Wavefunction::get_fdata.") % name;
        return *_wf_map[name];
      }

  };

}

#endif
